
#pragma once

#include <windows.h>
#include <stdio.h>   // use for file input/output
#include <string.h>  // useful string functions such as strlen(..)

#define DBG_HALT __asm{ int 0x10 }
#define DBG_ASSERT(exp) {if ( !(exp) ) {DBG_HALT;}}
const char g_validChars[] = "abcdefghijklmnopqrstuvwxyz"
							"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
							"1234567890"
							"\\,.:;{} \t\r\n<>-\"[]_/";

#pragma warning (disable : 4996)

/******************************************************************************/

inline bool CheckValidChars(const char* word, const char *vals)
{
	DBG_ASSERT(word);
	DBG_ASSERT(vals);
	unsigned int i=0;
	while(word[i])
	{
		bool ok=false;
		unsigned int j=0;
		while(vals[j])
		{
			if (word[i]==vals[j])
			{
				ok=true;
				break;
			}
			j++;
		}

		if (!ok)
		{
			return false;
		}

		i++;
	}
	return true;
}

/******************************************************************************/

inline bool CheckValidChar(const char letter, const char *vals)
{
	DBG_ASSERT(vals);
	bool ok=false;
	unsigned int j=0;
	while(vals[j])
	{
		if (letter==vals[j])
		{
			ok=true;
			break;
		}
		j++;
	}

	if (!ok) return false;

	return true;
}


/******************************************************************************/

struct stToken
{
	stToken() {m_tokenType=(TokenType)-1; m_data=NULL;}
	~stToken() 
	{ 
		if (m_data) 
		{ 
			delete m_data; 
			m_data=NULL;
		}  
	}

	stToken(const stToken& rhs)
	{
		(*this) = rhs;
	}

	void operator=(const stToken& rhs)
	{
		this->m_tokenType = rhs.m_tokenType;
		this->m_data = NULL;
		if (rhs.m_data)
		{
			int len = (int)strlen(rhs.m_data) + 1;
			this->m_data = new char[len];
			memset(this->m_data, 0, len);
			strcpy(this->m_data, rhs.m_data);
		}
	}

	enum TokenType
	{
		TokenString,		// "str.."
		TokenNumber,		// 0123456789.
		TokenOpenB,			// {
		TokenCloseB,		// }
		TokenComma,			// ,
		TokenSemiColon,		// ;
		TokenColon,			// :
	};

	TokenType	m_tokenType;
	char*		m_data;

	TokenType	GetType() {return m_tokenType;}

	static void SetToken(stToken& token, TokenType tokenType, char* strData=NULL)
	{
		token.m_tokenType   = tokenType;
		if (token.m_data) 
		{
			delete[] token.m_data;
			token.m_data = NULL;
		}
		if (strData)
		{
			int sizeData = (int)strlen(strData) + 1;
			token.m_data = new char[sizeData];
			memset(token.m_data, 0, sizeData);
			strcpy(token.m_data, strData);
		}
	}

};

/******************************************************************************/

class TokenParser
{
public:
	TokenParser(char* fileData, int sizeData)
	{
		bool valid = CheckValidChars(fileData, g_validChars);
		DBG_ASSERT(valid);

		m_fileData  = fileData;
		m_sizeData	= sizeData;
		m_indx		= 0;
		m_lineNo	= 0;
		m_data		= NULL;
	};

	stToken* GetToken()
	{
		return &m_token;
	}
	stToken* GetNextToken();
	stToken* PeekNextToken();
	int      GetLineNo(){return m_lineNo;}


	void	SetData(void* data) {m_data=data;	}
	void*	GetData()			{return m_data;	}

private:
	void*	m_data;

	char*	m_fileData;
	int		m_sizeData;
	int		m_indx;
	int		m_lineNo;
	stToken m_token;
	stToken m_tokenPeek;
};

/******************************************************************************/

stToken* TokenParser::PeekNextToken()
{
	int prevIndx	  = m_indx;
	stToken prevToken = m_token;
	m_tokenPeek = *GetNextToken();
	
	m_indx  = prevIndx;
	m_token = prevToken;
	return &m_tokenPeek;
}

/******************************************************************************/

stToken* TokenParser::GetNextToken()
{
	if (m_indx>(m_sizeData-1) || (m_fileData[m_indx]==0))
	{
		return NULL;
	}

	const int MAX_STRING_LEN = 128;
	static char buf[MAX_STRING_LEN] = {0};
	int  bufLen = 0;
	bool foundToken = false;

	while (m_indx<m_sizeData && m_fileData[m_indx])
	{
		char letter = m_fileData[m_indx];
		m_indx++;

		switch (letter)
		{
			case '"':
			{
				memset(buf, 0, sizeof(buf));
				bufLen = 0;
				while (m_fileData[m_indx] && m_fileData[m_indx]!='"')
				{
					buf[bufLen] = m_fileData[m_indx];
					bufLen++;
					DBG_ASSERT(bufLen<MAX_STRING_LEN-1);
					m_indx++;
				}
				m_indx++;
				stToken::SetToken(m_token, stToken::TokenString, buf);
			}
			break;

			case '{':	stToken::SetToken(m_token, stToken::TokenOpenB);		break;
			case '}':	stToken::SetToken(m_token, stToken::TokenCloseB);		break;	
			case ',':	stToken::SetToken(m_token, stToken::TokenComma);		break;
			case ';':	stToken::SetToken(m_token, stToken::TokenSemiColon);	break;
			case ':':	stToken::SetToken(m_token, stToken::TokenColon);		break;

			case '\n':
			{
				m_lineNo++;
				continue;
			}
			break;

			case '/': // Comment
			{
				DBG_ASSERT(m_fileData[m_indx]=='/');
				while (m_fileData[m_indx] && !CheckValidChar(m_fileData[m_indx], "\r\n"))
				{
					m_indx++;
				}
			}
			break;

			case 0:
			{
				DBG_HALT;
			}
			break;

			case '\r':
			case '\t':
			case ' ' :
			{
				continue;
			}
			break;

			default:
			{
				memset(buf, 0, sizeof(buf));
				bufLen = 0;
				buf[bufLen] = letter;
				bufLen++;
				while (m_fileData[m_indx] && !CheckValidChar(m_fileData[m_indx], "{},;: \r\n\t"))
				{
					buf[bufLen] = m_fileData[m_indx];
					bufLen++;
					DBG_ASSERT(bufLen<MAX_STRING_LEN-1);
					m_indx++;
				}

				bool isNum = CheckValidChars(buf, "-0123456789.");
				stToken::TokenType tokenType = (isNum ? stToken::TokenNumber : stToken::TokenString);
				stToken::SetToken(m_token, tokenType, buf);
			}
		}

		foundToken = true;
		break;
	}
	DBG_ASSERT(m_token.m_tokenType>=0);

	if (!foundToken)
	{
		return NULL;
	}

	return &m_token;
}